import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.Semaphore;

/**
 * MemoryManager.java
 * @author Will
 * Class used to keep up with available space in RAM.
 */
public class MemoryManager {
	public static ArrayList<Block> blocks = new ArrayList<Block>();
	public static Object lock = new Object();
	public static Semaphore s = new Semaphore(1, true);
	public static boolean DEFRAG = false;
	/**
	 * Called once to initialize the MemoryManager;
	 */
	public static void Initialize(){
		blocks.add(new Block(0, Driver.RAM_SIZE));
	}

	/**
	 * Finds the first block large enough to store the process.
	 * If no such block exists but there is enough space between
	 * all of the free blocks, the RAM is defragmented and then 
	 * the write occurs.
	 * 
	 * If all else fails, it sets the process state to WAITING
	 */
	public static boolean Write(Process p){
		try{
			if(!DEFRAG){
				s.acquire();										//Acquire A Lock				
			}
			int free = 0;										//Used to determine if we need to defrag the RAM. 										
			boolean waiter = false;								//Used to signify the process came from waiting queue.
			int waitPosition = 0;								//The Position a waiting process had in the waiting queue.
			ArrayList<Block> temp = new ArrayList<Block>();		//A temporary list of Blocks. Necessary to prevent concurrent editing.
			if(p.state == Process.State.WAITING){			//Was this Process Selected from the waiting Queue? 
				waiter = true;								//Yes, we have a waiter!
				waitPosition = Jobs.waiting.indexOf(p);		//So we don't loose it's place in line. This SHOULD always be 0.
				Jobs.waiting.remove(Jobs.waiting.indexOf(p));//Remove it from the waiting queue.
			}			
			for(int i = 0; i < blocks.size(); i++)				//Copying the contents of the real Block list into our temporary one.
				temp.add(blocks.get(i));
			for(Block b: temp){									//Iterate through our virtual Block List.
				if(b.available && b.blockSize >= p.size){		//Check to see if the process will fit in this block	
					p.ramBaseLocation = b.blockStart;			//Set the processes starting location in RAM 
					for(int i = 0; i < p.size; i++){			//Copy process data sequentially from disk to ram.
						RAM.Address[p.ramBaseLocation + i] = Disk.address[p.diskBaseAddress + i];
					}
					Jobs.ready.add(p);							//Add process to Ready Queue.
					b.Split(b, p.size);							//Split the block used to store the process into two. One used, one available.					
					p.inRam = true;								//Indicate that the process is loaded in ram.
					p.state = Process.State.READY;				//Set the State to READY					
					s.release();								//Release the lock
					return true;								
				}	
				else if (b.available && b.blockSize < p.size)	//The process size is to large for the block being considered
					free += b.blockSize;							//Add current block's size to free space.

			}
			if(free >= p.size){									//If we've gone through all of the blocks and none are large enough by themselves but
				Defragment();									//There's enough space between all of the incongruent free blocks, we Defragment the ram. 
				if(waiter)										//If this was a waiter....
					Jobs.waiting.add(waitPosition, p);			//Then put it back in line.
				if(DEFRAG)										//If the section of code is being evaulated, we still have a lock that needs to be released.				
					return Write(p);							//After defragging, resubmit the process recursively.
				else{
					p.state = Process.State.WAITING;				//If all else has failed there just isn't enough space in ram for our process
					if(waiter)										//If the process was already in the waiting queue....
						Jobs.waiting.add(waitPosition, p);			//Put it back in the same position. Again, this SHOULD always be at the front of the line.
					else											//Otherwise.....
						Jobs.waiting.add(p);						//Add it to the end of the waiting queue. 					
					s.release();
					return false;
				}
			}
			else{
				p.state = Process.State.WAITING;				//If all else has failed there just isn't enough space in ram for our process
				if(waiter)										//If the process was already in the waiting queue....
					Jobs.waiting.add(waitPosition, p);			//Put it back in the same position. Again, this SHOULD always be at the front of the line.
				else											//Otherwise.....
					Jobs.waiting.add(p);						//Add it to the end of the waiting queue. 
				s.release();									//Release the lock.
				return false;									
			}
		}
		catch (InterruptedException e) {
			// TODO Auto-generated catch block
			s.release();
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * Frees up the block being used by the terminating process.
	 * @param p
	 */
	public static void Remove(Process p){
		p.state = Process.State.TERMINATED;
		p.inRam = false;
		Jobs.finished.add(p);											//Add A Job to the Finished Jobs List		 
		for(Block b: blocks){
			if(b.blockStart == p.ramBaseLocation){			
				b.available = true;
				return;
			}
		}
	}
	
	private static void Defragment(){
		//RAM.lock();		
		DEFRAG = false;
		ArrayList<Block> freeblocks = new ArrayList<Block>();			//We're going to need to find all of our available blocks.
		for(Block b: blocks){											
			if(b.available)												//If The Block Is Available, add it to our list of free blocks.
				freeblocks.add(b);
		}

		if(freeblocks.size() > 1){										//Make sure we have more than one available block. If not, this will cause deadlock.
			for(int i = 0; i < freeblocks.size() -1 ; i++){																			
				if((freeblocks.get(i).blockSize 						//If the position just after the end of the current block is the starting position
					+ freeblocks.get(i).blockStart)  					//Of the next block, we merge them in the real blocks list.
					== freeblocks.get(i+1).blockStart){
					Block.Merge(freeblocks.get(i), freeblocks.get(i+1));
					DEFRAG = true;
					return;
				}
			}
		}			
		//RAM.release();
	}
	
	/**
	 * Private Wrapper Class Used to define blocks of memory.
	 * @author Will
	 */
	private static class Block{
		int blockStart, blockSize;
		boolean available = true;
		protected Block(int start, int size){
			blockStart = start;
			blockSize = size;
		}
		protected void Split(Block b, int usedSpace){
			int begin = b.blockStart;
			int size = b.blockSize; 
			ArrayList<Block> temp = new ArrayList<Block>();
			for(int i = 0; i < blocks.size(); i++)
				temp.add(blocks.get(i));
			//Get Rid of the old block.
			for(Block block: temp){
				if(block.blockStart == begin){
					blocks.remove(block);
				}
			}			
			//Create our new used block.
			blocks.add(new Block(begin, usedSpace));
			blocks.get(blocks.size() -1).available = false;
			//Create our new Free Block
			if(size != usedSpace)
				blocks.add(new Block(begin + usedSpace, size - usedSpace));
		}
		
		protected static void Merge(Block a, Block b){
/*
			System.out.println("Before");
			for(Block t: blocks){
				System.out.println("Block Starts: " + t.blockStart + " Block Ends: " + (t.blockStart + t.blockSize) + " Block Size: " + t.blockSize + " Available: "+t.available);
			}			
*/			
			Block merged = new Block(a.blockStart, (a.blockSize + b.blockSize));
			blocks.remove(a);
			blocks.remove(b);
			blocks.add(merged);
			/*
			System.out.println("After");
			for(Block t: blocks){
				System.out.println("Block Starts: " + t.blockStart + " Block Ends: " + (t.blockStart + t.blockSize) + " Block Size: " + t.blockSize + " Available: "+t.available);
			}			
*/			
		}
		
	}
	
	public static void dump(){		
		DateFormat df = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
		Date date = new Date();
		String filename = "./MemoryDumps/WimiOS_Dump_" + df.format(date) + ".txt";	
		try {			
			BufferedWriter br = new BufferedWriter(new FileWriter(filename));
			for(int i = 0; i < Driver.RAM_SIZE; i++){			
				br.write(("RAM[" + i + "]: " + RAM.Address[i] + "\r\n"));
				br.write("\n");
			}
			br.close();
		} 
		catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
	}
	
	public static void pcbDump(){
		DateFormat df = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
		Date date = new Date();
		String filename = "./MemoryDumps/PCB_Dump_" + df.format(date) + ".txt";	
		try {			
			BufferedWriter br = new BufferedWriter(new FileWriter(filename));
			for(int i = 0; i < Jobs.allJobs.size(); i++){			
				br.write(("Job[" + Jobs.allJobs.get(i).number + "] BaseRamLocation: " + Jobs.allJobs.get(i).ramBaseLocation + "\r\n"));
			}
			br.close();
		} 
		catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
	}
}


